#include "common.h"
#include "sim.h"
#include "trace.h" 
#include "cache.h"  /**** NEW-LAB2*/
#include "memory.h" // NEW-LAB2
#include <stdlib.h>
#include <ctype.h> /* Library for useful character operations */
#include "bpred.h"
#include "vmem.h"
/*******************************************************************/
/* Simulator frame */
/*******************************************************************/

#define HW_MAX_THREAD 4

bool run_a_cycle(memory_c *m); // please modify run_a_cycle function argument  /** NEW-LAB2 */
void init_structures(memory_c *m); // please modify init_structures function argument  /** NEW-LAB2 */

/* uop_pool related variables */

uint32_t free_op_num;
uint32_t active_op_num;
Op *op_pool;
Op *op_pool_free_head = NULL;
bool control_hazard_flag = false;
bool is_mispredicted = false;
uint64_t mispredicted_op_id = 0;
int ex_cycle_count = -1;
int mem_cycle_count = -1;
Cache *data_cache;  // NEW-LAB2
list<uint64_t> mem_latch_print_ops;
list<Op *> mem_latch_ops;
list<Op *> mem_latch_ops_delay;
list<uint8_t> WB_dst;
uint64_t bpred_mispred_count = 0; /* total number of branch mispredictions */ // NEW-LAB3
uint64_t bpred_okpred_count = 0; /* total number of correct branch predictions */ // NEW-LAB3
uint64_t dtlb_hit_count = 0; /* total number of DTLB hits */ // NEW-LAB3
uint64_t dtlb_miss_count = 0; /* total number of DTLB miss */ // NEW-LAB3
bool paddr_available = false;
bool pending_tlb_in_mshr = false;
uint8_t tlb_mem_request_available = 0;
uint64_t schedular_arbiter = 0;

//LAB5
//overall
UINT64 fp_instructions = 0;
UINT64 int_instructions = 0;
UINT64 branch_instructions = 0;
UINT64 load_instructions = 0;
UINT64 store_instructions = 0;
//fetch stage
UINT64 icache_read_accesses = 0;
//decode stage
UINT64 schedular_accesses = 0;
UINT64 int_reg_reads = 0;
UINT64 fp_reg_reads = 0;

//EXE stage
UINT64 mul_accesses = 0;

//MEM stage

UINT64 dcache_read_accesses = 0;
UINT64 dcache_write_accesses = 0;
UINT64 store_hit_count = 0;
UINT64 memory_accesses = 0;

UINT64 int_regfile_writes = 0;
UINT64 float_regfile_writes = 0;


// add the followings
bool br_stall[HW_MAX_THREAD] = {0};
uint64_t retired_instruction_thread[HW_MAX_THREAD] = {0};       // NEW for LAB4
uint64_t dcache_miss_count_thread[HW_MAX_THREAD] = {0};         // NEW for LAB4
uint64_t dcache_hit_count_thread[HW_MAX_THREAD] = {0};  // NEW for LAB4
uint64_t data_hazard_count_thread[HW_MAX_THREAD] = {0};         // NEW for LAB4
uint64_t control_hazard_count_thread[HW_MAX_THREAD] = {0};      // NEW for LAB4
uint64_t store_load_forwarding_count_thread[HW_MAX_THREAD] = {0};       // NEW for LAB4
uint64_t bpred_mispred_count_thread[HW_MAX_THREAD] = {0};       // NEW for LAB4
uint64_t bpred_okpred_count_thread[HW_MAX_THREAD] = {0};        // NEW for LAB4
uint64_t dtlb_hit_count_thread[HW_MAX_THREAD] = {0};    // NEW for LAB4
uint64_t dtlb_miss_count_thread[HW_MAX_THREAD] = {0};   // NEW for LAB4

bool control_hazard_flag_thread[HW_MAX_THREAD] = {0};
bool last_op_thread[HW_MAX_THREAD] = {false, false, false, false};
bool mispredicted_op_id_thread[HW_MAX_THREAD] = {0,0,0,0};

bpred *branchpred; // NEW-LAB3 (student need to initialize)
tlb *dtlb;        // NEW-LAB3 (student need to intialize)

/* simulator related local functions */

bool icache_access(ADDRINT addr);
bool dcache_access(ADDRINT addr);
void init_latches(void);

#include "knob.h"
#include "all_knobs.h"

// knob variables
KnobsContainer *g_knobsContainer; /* < knob container > */
all_knobs_c *g_knobs; /* < all knob variables > */

gzFile g_stream[HW_MAX_THREAD];

void init_knobs(int argc, char** argv) {
	// Create the knob managing class
	g_knobsContainer = new KnobsContainer();

	// Get a reference to the actual knobs for this component instance
	g_knobs = g_knobsContainer->getAllKnobs();

	// apply the supplied command line switches
	char* pInvalidArgument = NULL;
	g_knobsContainer->applyComandLineArguments(argc, argv, &pInvalidArgument);

	g_knobs->display();
}

void read_trace_file(void) {
  g_stream[0] = gzopen((KNOB(KNOB_TRACE_FILE)->getValue()).c_str(), "r");

  if ((KNOB(KNOB_RUN_THREAD_NUM)->getValue())<2) return;    /** NEW-LAB4 **/
   g_stream[1] = gzopen((KNOB(KNOB_TRACE_FILE2)->getValue()).c_str(), "r"); /** NEW-LAB4 **/
   if ((KNOB(KNOB_RUN_THREAD_NUM)->getValue())<3) return;  /** NEW-LAB4 **/
   g_stream[2] = gzopen((KNOB(KNOB_TRACE_FILE3)->getValue()).c_str(), "r"); /** NEW-LAB4 **/
   if ((KNOB(KNOB_RUN_THREAD_NUM)->getValue())<4) return;  /** NEW-LAB4 **/
   g_stream[3] = gzopen((KNOB(KNOB_TRACE_FILE4)->getValue()).c_str(), "r"); /** NEW-LAB4 **/
}

// simulator main function is called from outside of this file 

void simulator_main(int argc, char** argv) {
	init_knobs(argc, argv);

	mem_latch_print_ops.clear();
	mem_latch_ops.clear();
	mem_latch_ops_delay.clear();
	WB_dst.clear();

	// trace driven simulation
	read_trace_file();

	/** NEW-LAB2 *//* just note: passing main memory pointers is hack to mix up c++ objects and c-style code *//* Not recommended at all */
	memory_c *main_memory = new memory_c();  // /** NEW-LAB2 */
	main_memory->init_mem();

	init_structures(main_memory);
	if (KNOB(KNOB_USE_BPRED)->getValue()) {
		branchpred = bpred_new((bpred_type) KNOB(KNOB_BPRED_TYPE)->getValue(), KNOB(KNOB_BPRED_HIST_LEN)->getValue());
	}
	if (KNOB(KNOB_ENABLE_VMEM)->getValue()) {
		dtlb = tlb_new(KNOB(KNOB_TLB_ENTRIES)->getValue());
	}
	data_cache = new Cache();
	init_dcache();
	run_a_cycle(main_memory);

}
int op_latency[NUM_OP_TYPE];

void init_op_latency(void) {
	op_latency[OP_INV] = 1;
	op_latency[OP_NOP] = 1;
	op_latency[OP_CF] = 1;
	op_latency[OP_CMOV] = 1;
	op_latency[OP_LDA] = 1;
	op_latency[OP_LD] = 1;
	op_latency[OP_ST] = 1;
	op_latency[OP_IADD] = 1;
	op_latency[OP_IMUL] = 2;
	op_latency[OP_IDIV] = 4;
	op_latency[OP_ICMP] = 2;
	op_latency[OP_LOGIC] = 1;
	op_latency[OP_SHIFT] = 2;
	op_latency[OP_BYTE] = 1;
	op_latency[OP_MM] = 2;
	op_latency[OP_FMEM] = 2;
	op_latency[OP_FCF] = 1;
	op_latency[OP_FCVT] = 4;
	op_latency[OP_FADD] = 2;
	op_latency[OP_FMUL] = 4;
	op_latency[OP_FDIV] = 16;
	op_latency[OP_FCMP] = 2;
	op_latency[OP_FBIT] = 2;
	op_latency[OP_FCMP] = 2;
}

void init_op(Op *op) {
	op->num_src = 0;
	op->src[0] = -1;
	op->src[1] = -1;
	op->dst = -1;
	op->opcode = 0;
	op->is_fp = false;
	op->cf_type = NOT_CF;
	op->mem_type = NOT_MEM;
	op->write_flag = 0;
	op->inst_size = 0;
	op->ld_vaddr = 0;
	op->st_vaddr = 0;
	op->instruction_addr = 0;
	op->branch_target = 0;
	op->actually_taken = 0;
	op->mem_read_size = 0;
	op->mem_write_size = 0;
	op->valid = FALSE;
	op->is_tlb_miss_request = false;
	/* you might add more features here */
}

void init_op_pool(void) {
	/* initialize op pool */
	op_pool = new Op[1024];
	free_op_num = 1024;
	active_op_num = 0;
	uint32_t op_pool_entries = 0;
	int ii;
	for (ii = 0; ii < 1023; ii++) {

		op_pool[ii].op_pool_next = &op_pool[ii + 1];
		op_pool[ii].op_pool_id = op_pool_entries++;
		init_op(&op_pool[ii]);
	}
	op_pool[ii].op_pool_next = op_pool_free_head;
	op_pool[ii].op_pool_id = op_pool_entries++;
	init_op(&op_pool[ii]);
	op_pool_free_head = &op_pool[0];
}

Op *get_free_op(void) {
	/* return a free op from op pool */

	if (op_pool_free_head == NULL || (free_op_num == 1)) {
		std::cout << "ERROR! OP_POOL SIZE is too small!! " << endl;
		std::cout << "please check free_op function " << endl;
		assert(1);
		exit(1);
	}

	free_op_num--;
	assert(free_op_num);

	Op *new_op = op_pool_free_head;
	op_pool_free_head = new_op->op_pool_next;
	assert(!new_op->valid);
	init_op(new_op);
	active_op_num++;
	return new_op;
}

void free_op(Op *op) {
	free_op_num++;
	active_op_num--;
	op->valid = FALSE;
	op->op_pool_next = op_pool_free_head;
	op_pool_free_head = op;
}

/*******************************************************************/
/*  Data structure */
/*******************************************************************/

typedef struct pipeline_latch_struct {
	Op *op; /* you must update this data structure. */
	bool op_valid;
	bool last_op;
	/* you might add more data structures. But you should complete the above data elements */
} pipeline_latch;

typedef struct Reg_element_struct {
	bool valid;
	// data is not needed
	/* you might add more data structures. But you should complete the above data elements */
} REG_element;

REG_element register_file[HW_MAX_THREAD][NUM_REG];

/*******************************************************************/
/* These are the functions you'll have to write.  */
/*******************************************************************/

void FE_stage();
void ID_stage();
void EX_stage();
void MEM_stage(memory_c * main_memory);
void WB_stage(memory_c * main_memory);

/*******************************************************************/
/*  These are the variables you'll have to write.  */
/*******************************************************************/

bool sim_end_condition = FALSE; /* please complete the condition. */
UINT64 retired_instruction = 0; /* number of retired instruction. (only correct instructions) */
UINT64 cycle_count = 0; /* total number of cycles */
UINT64 data_hazard_count = 0;
UINT64 control_hazard_count = 0;
UINT64 icache_miss_count = 0; /* total number of icache misses. for Lab #2 and Lab #3 */
UINT64 dcache_miss_count = 0; /* total number of dcache  misses. for Lab #2 and Lab #3 */
UINT64 l2_cache_miss_count = 0; /* total number of L2 cache  misses. for Lab #2 and Lab #3 */
UINT64 dram_row_buffer_hit_count = 0; /* total number of dram row buffer hit. for Lab #2 and Lab #3 */ // NEW-LAB2
UINT64 dram_row_buffer_miss_count = 0; /* total number of dram row buffer hit. for Lab #2 and Lab #3 */ // NEW-LAB2
UINT64 store_load_forwarding_count = 0; /* total number of store load forwarding for Lab #2 and Lab #3 */ // NEW-LAB2
UINT64 dcache_hit_count = 0;

pipeline_latch *MEM_latch;
pipeline_latch *EX_latch;
pipeline_latch *ID_latch;
pipeline_latch *FE_latch;
pipeline_latch *schedular[HW_MAX_THREAD];
UINT64 ld_st_buffer[LD_ST_BUFFER_SIZE];
UINT64 next_pc;

/*******************************************************************/
/*  Print messages  */
/*******************************************************************/

void print_stats() {
  std::ofstream out((KNOB(KNOB_OUTPUT_FILE)->getValue()).c_str());
  /* Do not modify this function. This messages will be used for grading */

  out << "Total instruction: " << retired_instruction << endl;
  out << "Total cycles: " << cycle_count << endl;
  float ipc = (cycle_count ? ((float)retired_instruction/(float)cycle_count): 0 );
  out << "Total IPC: " << ipc << endl;
  out << "Total D-cache miss: " << dcache_miss_count << endl;
  out << "Total D-cache hit: " << dcache_hit_count << endl;
  out << "Total data hazard: " << data_hazard_count << endl;
  out << "Total control hazard : " << control_hazard_count << endl;
  out << "Total DRAM ROW BUFFER Hit: " << dram_row_buffer_hit_count << endl;
  out << "Total DRAM ROW BUFFER Miss: "<< dram_row_buffer_miss_count << endl;
  out << "Total Store-load forwarding: " << store_load_forwarding_count << endl;
  out << "Total Branch Predictor Mispredictions: " << bpred_mispred_count << endl;
  out << "Total Branch Predictor OK predictions: " << bpred_okpred_count << endl;
  out << "Total DTLB Hit: " << dtlb_hit_count << endl;
  out << "Total DTLB Miss: " << dtlb_miss_count << endl;

  out << endl << endl << endl;

  for (int ii = 0; ii < (KNOB(KNOB_RUN_THREAD_NUM)->getValue()); ii++ ) {
    out << "THREAD instruction: " << retired_instruction_thread[ii] << " Thread id: " << ii << endl;
    float thread_ipc = (cycle_count ? ((float)retired_instruction_thread[ii]/(float)cycle_count): 0 );
    out << "THREAD IPC: " << thread_ipc << " Thread id: " << ii << endl ;
    out << "THREAD D-cache miss: " << dcache_miss_count_thread[ii] << " Thread id: " << ii << endl;
    out << "THREAD D-cache hit: " << dcache_hit_count_thread[ii] << " Thread id: " << ii << endl;
    out << "THREAD data hazard: " << data_hazard_count_thread[ii] << " Thread id: " << ii <<   endl;
    out << "THREAD control hazard : " << control_hazard_count_thread[ii] << " Thread id: " << ii << endl;
    out << "THREAD Store-load forwarding: " << store_load_forwarding_count_thread[ii] << " Thread id: " << ii << endl;
    out << "THREAD Branch Predictor Mispredictions: " << bpred_mispred_count_thread[ii] << " Thread id: " << ii << endl;
    out << "THREAD Branch Predictor OK predictions: " << bpred_okpred_count_thread[ii] << " Thread id: " << ii << endl;
    out << "THREAD DTLB Hit: " << dtlb_hit_count_thread[ii] << " Thread id: " << ii << endl;
    out << "THREAD DTLB Miss: " << dtlb_miss_count_thread[ii] << " Thread id: " << ii << endl;
  }
  out.close();
}


void print_counters_xml(){
	std::ofstream out("power_counters.txt");
    /* Do not modify this function. This messages will be used for grading */
    out << "core0:number_hardware_threads " << KNOB(KNOB_RUN_THREAD_NUM)->getValue() << endl;
    out << "PBT:global_predictor_entries " << KNOB(KNOB_BPRED_HIST_LEN)->getValue() << endl;
    //out << "Dcache Size(Line # 230, 213): dcache:dcache_config icache:icache_config " << KNOB(KNOB_DCACHE_SIZE)->getValue() << endl;
    //out << endl << "Overall:" << endl;
    out << "core0:total_instructions core0:committed_instructions " << retired_instruction << endl;
    out << "core0:total_cycles core0:busy_cycles " << cycle_count << endl;
    out << "core0:fp_instructions core0:committed_fp_instructions " << fp_instructions << endl;
    out << "core0:int_instructions core0:committed_int_instructions " << int_instructions << endl;
    out << "core0:branch_instructions " << bpred_mispred_count + bpred_okpred_count << endl;
    out << "core0:load_instructions " << load_instructions << endl;
    out << "core0:store_instructions " << store_instructions << endl;

    //out << endl << "Fetch Stage:" << endl;
    out << "icache:read_accesses " << retired_instruction << endl;       /*!!!!!!!!*/
    out << "core0:branch_mispredictions " << bpred_mispred_count << endl;

    //out << endl << "Decode Stage:" << endl;
    out << "core0:ROB_reads core0:ROB_writes core0:rename_accesses core0:fp_rename_accesses core0:inst_window_reads core0:inst_window_writes core0:inst_window_wakeup_accesses core0:fp_inst_window_reads core0:fp_inst_window_writes core0:fp_inst_window_wakeup_accesses " << schedular_accesses << endl;
    out << "core0:int_regfile_reads core0:ialu_accesses " << int_reg_reads << endl;
    out << "core0:float_regfile_reads core0:fpu_accesses " << fp_reg_reads << endl;

    //out << endl << "Execution Stage:" << endl;
    out << "core0:mul_accesses " << mul_accesses << endl;

    //out << endl << "Memory:" << endl;
    out << "dtlb:total_accesses " << dcache_miss_count+dcache_hit_count << endl;
    out << "dcache:read_accesses " << dcache_hit_count - store_hit_count << endl;
    out << "dcache:write_accesses " << dcache_miss_count+store_hit_count << endl;
    out << "mem:memory_accesses mem:memory_reads mem:memory_writes " << dcache_miss_count << endl;
    out << "mc:memory_reads mc:memory_writes " << 2 * dcache_miss_count << endl;
    out << "noc0:total_accesses mc:memory_accesses " <<  4* dcache_miss_count << endl;

    //out << endl << "WB:" << endl;
    out << "int_regfile_writes " << int_regfile_writes << endl;
    out << "float_regfile_writes " << float_regfile_writes << endl;
    out << "system:target_core_clockrate " << KNOB(KNOB_CLOCK_FREQ)->getValue() << endl;
    out.close();
}

void print_counters(){
	print_counters_xml();
	/*
	std::ofstream out("power_counters.txt");
	  out << "core0:" << KNOB(KNOB_RUN_THREAD_NUM)->getValue() << endl;
	  out << "History Length(Line # 192): " << KNOB(KNOB_BPRED_HIST_LEN)->getValue() << endl;
	  out << "Dcache Size(Line # 230, 213): " << KNOB(KNOB_DCACHE_SIZE)->getValue() << endl;
	  out << endl << "Overall:" << endl;
	  out << "Total instruction (116, 123): " << retired_instruction << endl;
	  out << "Total cycles (129, 131): " << cycle_count << endl;
	  out << "Total Floating Point Operations (118, 125): " << fp_instructions << endl;
	  out << "Total Integer Operations (117, 124): " << int_instructions << endl;
	  out << "Total Branch Operations (119): " << bpred_mispred_count + bpred_okpred_count << endl;
	  out << "Total Load Operations (121): " << load_instructions << endl;
	  out << "Total Store Operations (122): " << store_instructions << endl;

	  out << endl << "Fetch Stage:" << endl;
	  out << "Total I-cache access (218): " << retired_instruction << endl;
	  out << "Total Branch mispredictions (120): " << bpred_mispred_count << endl;

	  out << endl << "Decode Stage:" << endl;
	  out << "Total Schedular Accesses (136 - 148): " << schedular_accesses << endl;
	  out << "Total Register reads for Integer Ops (150, 160): " << int_reg_reads << endl;
	  out << "Total Register reads for FP Ops (151, 161): " << fp_reg_reads << endl;

	  out << endl << "Execution Stage:" << endl;
	  out << "Total multiply instructions (162): " << mul_accesses << endl;

	  out << endl << "Memory:" << endl;
	  out << "Total data cache reads (224, 233): " << dcache_miss_count+dcache_hit_count << endl;
	  out << "Total data cache writes (\", 234): " << dcache_miss_count+store_hit_count << endl;
	  out << "Total memory accesses (344 (4x), 370, 371, 372, 393(4x), 394(2x), 395(2x)): " << dcache_miss_count << endl;

	  out << endl << "WB:" << endl;
	  out << "Total Integer reg_file writes (152): " << int_regfile_writes << endl;
	  out << "Total FP reg_file writes (153): " << float_regfile_writes << endl;

	  out.close();
	  */
}

/*******************************************************************/
/*  Support Functions  */
/*******************************************************************/

int get_op(Op *op)
{
  static UINT64 unique_count = 0;
  static UINT64 fetch_arbiter;

  Trace_op trace_op;
  bool success = FALSE;
  // read trace
  // fill out op info
  // return FALSE if the end of trace
  int read_size;
  int fetch_id = -1;
  bool br_stall_fail = false;
  // read trace
  // fill out op info
  // return FALSE if the end of trace
  for (int jj = 0; jj < (KNOB(KNOB_RUN_THREAD_NUM)->getValue()); jj++) {
    fetch_id = (fetch_arbiter++%(KNOB(KNOB_RUN_THREAD_NUM)->getValue()));

    if (br_stall[fetch_id]) {
    br_stall_fail = true;
    continue;
    }

    read_size = gzread(g_stream[fetch_id], &trace_op, sizeof(Trace_op));
    success = read_size>0;
    if(read_size!=sizeof(Trace_op) && read_size>0) {
      printf( "ERROR!! gzread reads corrupted op! @cycle:%d\n", cycle_count);
      success = false;
    }

    /* copy trace structure to op */
    if (success) {
	copy_trace_op(&trace_op, op);

	op->inst_id  = unique_count++;
	op->valid    = TRUE;
	op->thread_id = fetch_id;
	    return success;  // get op so return
    }

	// if not success and go to another trace.
}
if (br_stall_fail) return -1;
return success;
}

int get_op_latency(Op *op) {
	assert(op->opcode < NUM_OP_TYPE);
	return op_latency[op->opcode];
}

/* Print out all the register values */
void dump_reg() {
	for(int jj = 0; jj < HW_MAX_THREAD; jj++){
		for (int ii = 0; ii < NUM_REG; ii++) {
			std::cout << cycle_count << ":register[" << ii << "]: V:" << register_file[jj][ii].valid << endl;
		}
	}
}

void print_pipeline() {
	std::cout << "--------------------------------------------" << endl;
	std::cout << "cycle count : " << dec << cycle_count
			<< " retired_instruction : " << retired_instruction << endl;
	std::cout << (int) cycle_count << " FE: ";
	if (FE_latch->op_valid) {
		Op *op = FE_latch->op;
		cout << (int) op->inst_id;
	} else {
		cout << "####";
	}
	std::cout << " ID: ";
	if (ID_latch->op_valid) {
		Op *op = ID_latch->op;
		cout << (int) op->inst_id;
	} else {
		cout << "####";
	}
	std::cout << " EX: ";
	if (EX_latch->op_valid) {
		Op *op = EX_latch->op;
		cout << (int) op->inst_id;
	} else {
		cout << "####";
	}

	//std::cout << " MEM ";
	if (!mem_latch_print_ops.empty()) {
		while (!mem_latch_print_ops.empty()) {
			cout << " MEM_MSHR: " << (uint64_t) mem_latch_print_ops.front();
			mem_latch_print_ops.pop_front();
		}
	} else {
		cout << " MEM: ####";
	}
	cout << endl;
	//  dump_reg();
	std::cout << "--------------------------------------------" << endl;
}

void print_heartbeat()
{
  static uint64_t last_cycle_thread[HW_MAX_THREAD] ;
  static uint64_t last_inst_count_thread[HW_MAX_THREAD];

  for (int ii = 0; ii < (KNOB(KNOB_RUN_THREAD_NUM)->getValue()); ii++ ) {

    float temp_ipc = float(retired_instruction_thread[ii] - last_inst_count_thread[ii]) /(float)(cycle_count-last_cycle_thread[ii]) ;
    float ipc = float(retired_instruction_thread[ii]) /(float)(cycle_count) ;
    /* Do not modify this function. This messages will be used for grading */
    cout <<"**Heartbeat** cycle_count: " << cycle_count << " inst:" << retired_instruction_thread[ii] << " IPC: " << temp_ipc << " Overall IPC: " << ipc << " thread_id " << ii << endl;
    last_cycle_thread[ii] = cycle_count;
    last_inst_count_thread[ii] = retired_instruction_thread[ii];
  }
}
/*******************************************************************/
/*                                                                 */
/*******************************************************************/

bool run_a_cycle(memory_c *main_memory) {

	for (;;) {
		if (((KNOB(KNOB_MAX_SIM_COUNT)->getValue()
				&& (cycle_count >= KNOB(KNOB_MAX_SIM_COUNT)->getValue()))
				|| (KNOB(KNOB_MAX_INST_COUNT)->getValue()
						&& (retired_instruction
								>= KNOB(KNOB_MAX_INST_COUNT)->getValue()))
				|| (sim_end_condition))) {
			// please complete sim_end_condition
			// finish the simulation
			print_heartbeat();
			for(int i = 0; i < KNOB(KNOB_RUN_THREAD_NUM)->getValue(); i++){
				dcache_miss_count = dcache_miss_count + dcache_miss_count_thread[i];
				dcache_hit_count = dcache_hit_count + dcache_hit_count_thread[i];
				data_hazard_count = data_hazard_count + data_hazard_count_thread[i];
				control_hazard_count = control_hazard_count + control_hazard_count_thread[i];
				store_load_forwarding_count = store_load_forwarding_count + store_load_forwarding_count_thread[i];
				bpred_mispred_count = bpred_mispred_count + bpred_mispred_count_thread[i];
				bpred_okpred_count = bpred_okpred_count + bpred_okpred_count_thread[i];
				dtlb_hit_count = dtlb_hit_count + dtlb_hit_count_thread[i];
				dtlb_miss_count = dtlb_miss_count + dtlb_miss_count_thread[i];
			}
			print_stats();
			print_counters();
			return TRUE;
		}
		cycle_count++;
		if (!(cycle_count % 5000)) {
			print_heartbeat();
		}

		main_memory->run_a_cycle();          // *NEW-LAB2

		WB_stage(main_memory);
		MEM_stage(main_memory);
		EX_stage();
		ID_stage();
		FE_stage();
		if (KNOB(KNOB_PRINT_PIPE_FREQ)->getValue()
				&& !(cycle_count % KNOB(KNOB_PRINT_PIPE_FREQ)->getValue()))
			print_pipeline();
	}
	return TRUE;
}

/*******************************************************************/
/* Complete the following fuctions.  */
/* You can add new data structures and also new elements to Op, Pipeline_latch data structure */
/*******************************************************************/

void init_structures(memory_c *main_memory) {
	init_op_pool();
	init_op_latency();
	/* please initialize other data stucturs */
	/* you must complete the function */
	for(int j = 0; j < HW_MAX_THREAD; j++){
		for (uint32_t i = 0; i < 32; i++) {
			register_file[j][i].valid = true;
		}
	}
	init_latches();
}

void WB_stage(memory_c * main_memory) {
	/* You MUST call free_op function here after an op is retired */
	/* you must complete the function */

//if (!(MEM_latch->op_valid)) {
//		return;
//	}
	bool found_here = false;
/*	while (!WB_dst.empty()) {		//WB_dst is useless...can take it off
		register_file[WB_dst.front()].valid = true;
		WB_dst.pop_front();
	}*/
	if (MEM_latch->op != NULL) {
		MEM_latch->op_valid = false;
		Op * op = MEM_latch->op;
		//
		if (op->dst != -1) {		//TODO
			register_file[op->thread_id][op->dst].valid = true;
		}

		if (!KNOB(KNOB_USE_BPRED)->getValue() && (op->cf_type) >= CF_BR) {
			control_hazard_flag = false;
		} else if (KNOB(KNOB_USE_BPRED)->getValue() && op->cf_type == CF_CBR) {
			br_stall[op->thread_id] = false;
			is_mispredicted = !(op->inst_id >= mispredicted_op_id_thread[op->thread_id]);
		}

		retired_instruction++;
		retired_instruction_thread[op->thread_id]++;
		free_op(op);
		MEM_latch->op = NULL;
	}
	while (!mem_latch_ops.empty()) {
		Op* op = mem_latch_ops.front();
		mem_latch_ops.pop_front();

		if (op->dst != -1) {		//TODO
			register_file[op->thread_id][op->dst].valid = true;
			//WB_dst.push_back(op->dst);
		}

		if (!KNOB(KNOB_USE_BPRED)->getValue() && (op->cf_type) >= CF_BR) {
			control_hazard_flag = false;

		} else if (KNOB(KNOB_USE_BPRED)->getValue() && op->cf_type == CF_CBR) {
			br_stall[op->thread_id] = false;
			is_mispredicted = false;
		}

		retired_instruction++;
		retired_instruction_thread[op->thread_id]++;
		free_op(op);
		found_here = true;
	}
	while (!mem_latch_ops_delay.empty()) {
		mem_latch_ops.push_back(mem_latch_ops_delay.front());
		mem_latch_print_ops.push_back(mem_latch_ops_delay.front()->inst_id);
		mem_latch_ops_delay.pop_front();
	}
	MEM_latch->op_valid = false;

	if (MEM_latch->last_op && mem_latch_ops.empty() && main_memory->m_mshr.empty()) {
		sim_end_condition = true;
	}


}

void MEM_stage(memory_c *main_memory) {
	/* you must complete the function */

	if (MEM_latch->op_valid) {
		return;
	}

	if (EX_latch->last_op) {
		MEM_latch->last_op = true;
		return;
	}
	if (!(EX_latch->op_valid)) {
		return;
	}

	Op* op = EX_latch->op;
	if(op->mem_type == NOT_MEM){
		EX_latch->op_valid = false;
		MEM_latch->op = op;
		mem_latch_print_ops.push_back(op->inst_id);
		MEM_latch->op_valid = true;
		mem_cycle_count = -1;
		return;
	}

	if (!paddr_available && KNOB(KNOB_ENABLE_VMEM)->getValue()) {
		uint64_t vaddr = (op->mem_type == MEM_LD) ? op->ld_vaddr : op->st_vaddr;
		uint64_t vpn = vaddr
				>> (LOG2(KNOB(KNOB_VMEM_PAGE_SIZE)->getValue()));
		uint64_t pte_addr = vmem_get_pteaddr(vpn, op->thread_id);
		uint64_t pfn = vmem_vpn_to_pfn(vpn, op->thread_id);
		uint64_t paddr = (pfn << (LOG2(KNOB(KNOB_VMEM_PAGE_SIZE)->getValue()))) | (vaddr & ((1 << (LOG2(KNOB(KNOB_VMEM_PAGE_SIZE)->getValue()))) - 1));



		if (mem_cycle_count == -1 && !pending_tlb_in_mshr) {
			if ((op->mem_type == MEM_LD) || (op->mem_type == MEM_ST)) {
				if (tlb_access(dtlb, vpn, op->thread_id, &pfn)) {
					op->ld_vaddr = paddr;
					op->st_vaddr = paddr;
					paddr_available = true;
					mem_cycle_count = -1;
					dtlb_hit_count_thread[op->thread_id]++;
				} else {
					mem_cycle_count = KNOB(KNOB_DCACHE_LATENCY)->getValue();
					dtlb_miss_count_thread[op->thread_id]++;

				}
			}
		}

		if (tlb_mem_request_available == 1) {
			tlb_mem_request_available = 0;
			tlb_install(dtlb, vpn, op->thread_id, pfn);
			paddr_available = true;
			op->ld_vaddr = paddr;
			op->st_vaddr = paddr;
			mem_cycle_count = -1;
			pending_tlb_in_mshr = false;
		} else if (tlb_mem_request_available == 2){
			tlb_mem_request_available = 1;
		}

		if ((mem_cycle_count == 1) && !pending_tlb_in_mshr) {
			if ((op->mem_type == MEM_LD) || (op->mem_type == MEM_ST)) {
				Op * tlb_op = (Op *) malloc(sizeof(Op));
				tlb_op->mem_type = MEM_LD;
				tlb_op->ld_vaddr = pte_addr;
				tlb_op->mem_read_size = VMEM_PTE_SIZE;
				tlb_op->is_tlb_miss_request = true;

				if (dcache_access(pte_addr)) {
					op->ld_vaddr = paddr;
					op->st_vaddr = paddr;
					tlb_install(dtlb, vpn, op->thread_id, pfn);
					paddr_available = true;
					mem_cycle_count = -1;
					dcache_hit_count_thread[op->thread_id]++;
				} else {
					dcache_miss_count_thread[op->thread_id]++;
					m_mshr_entry_s *entry = main_memory->search_matching_mshr(pte_addr);
					if (entry != NULL) {
						if (main_memory->store_load_forwarding(tlb_op)) {
							op->ld_vaddr = paddr;
							op->st_vaddr = paddr;
							tlb_install(dtlb, vpn, op->thread_id, pfn);
							paddr_available = true;
							store_load_forwarding_count_thread[op->thread_id]++;
						} else {
							entry->req_ops.push_back(tlb_op);
							pending_tlb_in_mshr = true;
							paddr_available = false;
						}
						mem_cycle_count = -1;
					} else {
						pending_tlb_in_mshr = main_memory->insert_mshr(tlb_op);
					}
					return;
				}
			}
		} else if (mem_cycle_count > 1 && !pending_tlb_in_mshr) {
			mem_cycle_count--;
		}
	}
//************ This executes the memory op if physical address is availabe or if virtual memory is disabled
	if ((paddr_available || !(KNOB(KNOB_ENABLE_VMEM)->getValue()))) {
		if (mem_cycle_count == -1) {
			mem_cycle_count =
					(op->mem_type == NOT_MEM) ?
							1 : KNOB(KNOB_DCACHE_LATENCY)->getValue();
		}

		if (mem_cycle_count == 1) {
			if ((op->mem_type == MEM_LD) && (!dcache_access(op->ld_vaddr))) {
				dcache_miss_count_thread[op->thread_id]++;
				m_mshr_entry_s *entry = main_memory->search_matching_mshr(
						op->ld_vaddr);
				if (entry != NULL) {
					if (main_memory->store_load_forwarding(op)) {
						MEM_latch->op_valid = true;
						MEM_latch->op = op;
						mem_latch_print_ops.push_back(op->inst_id);
						store_load_forwarding_count_thread[op->thread_id]++;
					} else {
						entry->req_ops.push_back(op);
					}
					mem_cycle_count = -1;
					EX_latch->op_valid = false;
					paddr_available = false;
				} else {
					EX_latch->op_valid = !main_memory->insert_mshr(op);
					if (!EX_latch->op_valid) {
						mem_cycle_count = -1;
						paddr_available = false;
					}
				}
				return;
			} else if ((op->mem_type == MEM_ST) && (!dcache_access(op->st_vaddr))) {
				dcache_miss_count_thread[op->thread_id]++;
				if (main_memory->check_piggyback(op)) {
					EX_latch->op_valid = false;
				} else {
					EX_latch->op_valid = !main_memory->insert_mshr(op);
				}
				if (!EX_latch->op_valid) {
					mem_cycle_count = -1;
					paddr_available = false;
				}
				return;
			}
			if ((op->mem_type == MEM_LD) || (op->mem_type == MEM_ST)) {
				if(op->mem_type == MEM_ST){
					store_hit_count++;
				}
				dcache_hit_count++;
			}
			EX_latch->op_valid = false;
			//mem_latch_ops.push_back(op);
			MEM_latch->op = op;
			mem_latch_print_ops.push_back(op->inst_id);
			MEM_latch->op_valid = true;
			mem_cycle_count = -1;
			paddr_available = false;
		} else {
			mem_cycle_count--;
		}
	}

}

void EX_stage() {
	/* you must complete the function */

	if (EX_latch->op_valid) {
		return;
	}

	if (ID_latch->last_op) {
		EX_latch->last_op = true;
		return;
	}
	if (!(ID_latch->op_valid)) {
		return;
	}

	Op *op = ID_latch->op;
	if (ex_cycle_count == -1) {
		ex_cycle_count = get_op_latency(op);
	}
	if (ex_cycle_count == 1) {
		ID_latch->op_valid = false;
		EX_latch->op_valid = true;
		EX_latch->op = op;
		ex_cycle_count = -1;
	} else {
		ex_cycle_count--;
	}

	//cycle_count += get_op_latency(op) - 1;

}

void ID_stage() {
	/* you must complete the function */
	if(retired_instruction_thread[1] >= 1743632){
		bool temp = false;
		temp = true;
	}
	bool temp = false;
	for(int j = 0; j < KNOB(KNOB_RUN_THREAD_NUM)->getValue(); j++){
		temp = temp | schedular[j]->op_valid;
	}
	if(!temp && FE_latch->last_op){
		ID_latch->last_op = true;
		return;
	}
	if ((FE_latch->op_valid)){
		Op *op = FE_latch->op;
		if(!(schedular[op->thread_id]->op_valid)){
			schedular[op->thread_id]->op = op;
			schedular[op->thread_id]->op_valid = true;
			FE_latch->op_valid = false;
		}
	}


	for(uint8_t i = 0; i < KNOB(KNOB_RUN_THREAD_NUM)->getValue(); i++){
		if(!(schedular[i]->op_valid)){
			continue;
		}
		Op * op = schedular[i]->op;

		bool temp_data_hazard = false;

		if ((op->cf_type) >= CF_BR) {
			if (KNOB(KNOB_USE_BPRED)->getValue()) {
				if(br_stall[i]){
					control_hazard_flag_thread[i] = true;
					control_hazard_count_thread[i]++;
				}
			} else {
				control_hazard_count_thread[i]++;
				control_hazard_flag_thread[i] = true;
			}
		}
		for (int ii = 0; ii < op->num_src; ii++) {
			if (!(register_file[op->thread_id][op->src[ii]].valid)) {
				temp_data_hazard = true;
			}
		}
		if (temp_data_hazard) {
			data_hazard_count_thread[i]++;
		}


	}

	if (ID_latch->op_valid) {
		return;
	}

	bool op_moved = false;
	uint8_t traversed_count = 0;
	bool temp_data_hazard = false;

	while(!op_moved && traversed_count++ != KNOB(KNOB_RUN_THREAD_NUM)->getValue()){
		if(!(schedular[(schedular_arbiter++)%KNOB(KNOB_RUN_THREAD_NUM)->getValue()]->op_valid)){
			continue;
		}
		Op * op = schedular[(schedular_arbiter-1)%KNOB(KNOB_RUN_THREAD_NUM)->getValue()]->op;
		schedular_accesses++;
		bool my_last_op = schedular[(schedular_arbiter-1)%KNOB(KNOB_RUN_THREAD_NUM)->getValue()]->last_op;
		if(my_last_op){
			continue;
		}
		for (int i = 0; i < op->num_src; i++) {
			if (!(register_file[op->thread_id][op->src[i]].valid)) {
				temp_data_hazard = true;
			}
		}
		if(!temp_data_hazard){
			ID_latch->op = op;
			ID_latch->op_valid = true;
			schedular[op->thread_id]->op_valid = false;
			op_moved = true;
			if (op->dst != -1) {
				register_file[op->thread_id][op->dst].valid = false;
			}
		}
	}
}

void FE_stage() {
	/* only part of FE_stage function is implemented */
	/* please complete the rest of FE_stage function */
	Op *op;

	if (!(FE_latch->op_valid)) {
		op = get_free_op();
		if (!get_op(op)) {
			FE_latch->last_op = true;
			FE_latch->op_valid = true;
			return;
		}
		if(op->is_fp){
			fp_instructions++;
			fp_reg_reads += op->num_src > 0 ? op->num_src : 0;
			float_regfile_writes += op->dst != -1 ? 1 : 0;
		}
		else if(op->opcode >= OP_IADD || op->opcode <= OP_IDIV){
			int_instructions++;
			int_reg_reads += op->num_src > 0 ? op->num_src : 0;
			int_regfile_writes += op->dst != -1 ? 1 : 0;

		}
		if(op->opcode == OP_IMUL || op->opcode == OP_FMUL){
			mul_accesses++;
		}
		if (op->mem_type == MEM_LD){
			load_instructions++;
		} else if (op->mem_type == MEM_ST){
			store_instructions++;
		}
	} else {
		return;
	}
	if (icache_access(op->instruction_addr)) {		//icache access
		if (KNOB(KNOB_USE_BPRED)->getValue() && op->cf_type == CF_CBR) {
			int prediction = bpred_access(branchpred, op->instruction_addr, op->thread_id);
			bpred_update(branchpred, op->instruction_addr, prediction, op->actually_taken, op->thread_id);
			if (prediction != op->actually_taken) {
				mispredicted_op_id_thread[op->thread_id] = op->inst_id;
				bpred_mispred_count_thread[op->thread_id]++;
				br_stall[op->thread_id] = true;
			} else {
				bpred_okpred_count_thread[op->thread_id]++;
			}
		}
		FE_latch->op = op;
		FE_latch->op_valid = true;
	} else {
		icache_miss_count++;		// icache access miss...should return??
	}

	//   next_pc = pc + op->inst_size;  // you need this code for building a branch predictor

}

void init_latches() {
	MEM_latch = new pipeline_latch();
	EX_latch = new pipeline_latch();
	ID_latch = new pipeline_latch();
	FE_latch = new pipeline_latch();

	for(int i = 0; i < HW_MAX_THREAD; i++){
		schedular[i] = new pipeline_latch();
		schedular[i]->op = NULL;
		schedular[i]->op_valid = false;
		schedular[i]->last_op = false;
	}


	MEM_latch->op = NULL;
	EX_latch->op = NULL;
	ID_latch->op = NULL;
	FE_latch->op = NULL;

	/* you must set valid value correctly  */
	MEM_latch->op_valid = false;
	EX_latch->op_valid = false;
	ID_latch->op_valid = false;
	FE_latch->op_valid = false;

	MEM_latch->last_op = false;
	EX_latch->last_op = false;
	ID_latch->last_op = false;
	FE_latch->last_op = false;

}

bool icache_access(ADDRINT addr) {

	/* For Lab #1, you assume that all I-cache hit */
	bool hit = FALSE;
	if (KNOB(KNOB_PERFECT_ICACHE)->getValue())
		hit = TRUE;
	return hit;
}

bool dcache_access(ADDRINT addr) {
	/* For Lab #1, you assume that all D-cache hit */
	bool hit = FALSE;
	/* For Lab #2, you need to connect cache here */   // NEW-LAB2
	if (KNOB(KNOB_PERFECT_DCACHE)->getValue()) {
		hit = TRUE;
	} else {
		hit = cache_access(data_cache, addr) == HIT ? TRUE : FALSE;
	}
	return hit;
}

void init_dcache() {
	cache_init(data_cache, KNOB(KNOB_DCACHE_SIZE)->getValue(), 64,
			KNOB(KNOB_DCACHE_WAY)->getValue(), "data_cache");
}

// NEW-LAB2
void dcache_insert(ADDRINT addr)  // NEW-LAB2
		{                                 // NEW-LAB2
	/* dcache insert function */   // NEW-LAB2
	cache_insert(data_cache, addr);   // NEW-LAB2

}                                       // NEW-LAB2

void broadcast_rdy_op(Op* op)             // NEW-LAB2
		{                                          // NEW-LAB2
	/* you must complete the function */     // NEW-LAB2
	// mem ops are done.  move the op into WB stage   // NEW-LAB2
	if (op->is_tlb_miss_request) {
		tlb_mem_request_available = 2;
		return;
	}
	MEM_latch->op_valid = true;
	mem_latch_ops_delay.push_back(op);
}      // NEW-LAB2

/* utility functions that you might want to implement */// NEW-LAB2
int get_dram_row_id(ADDRINT addr)    // NEW-LAB2
		{  // NEW-LAB2
		   // NEW-LAB2
	/* utility functions that you might want to implement */		 // NEW-LAB2
	/* if you want to use it, you should find the right math! */// NEW-LAB2
	/* pleaes carefull with that DRAM_PAGE_SIZE UNIT !!! */// NEW-LAB2
	// addr >> 6;   // NEW-LAB2
	return ((addr >> 0) / ((KNOB(KNOB_DRAM_PAGE_SIZE)->getValue()) * 1024));
}  // NEW-LAB2

int get_dram_bank_id(ADDRINT addr)  // NEW-LAB2
		{  // NEW-LAB2
		   // NEW-LAB2
	/* utility functions that you might want to implement */		 // NEW-LAB2
	/* if you want to use it, you should find the right math! */// NEW-LAB2
	// (addr >> 6);   // NEW-LAB2
	return (addr / ((KNOB(KNOB_DRAM_PAGE_SIZE)->getValue()) * 1024))
			% (KNOB(KNOB_DRAM_BANK_NUM)->getValue());
}  // NEW-LAB2

